﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Argos.Data.Model;

namespace Argos
{
	internal static class Reflector
	{

		#region public static void CopyObjectProperties(object fromInstance, object toInstance, Predicate<PropertyInfo> propertyMatcher)

		/// <summary>
		/// Copies the property values from one instance to another instance.
		/// </summary>
		/// <param name="fromInstance">The object instance to copy from.</param>
		/// <param name="toInstance">The object instance to copy to.</param>
		/// <param name="propertyMatcher">A predicate that matches the properties to copy.</param>
		public static void CopyObjectProperties(object fromInstance, object toInstance, Predicate<PropertyInfo> propertyMatcher)
		{
			Dictionary<string, object> props = new Dictionary<string, object>();
			Reflector.GetObjectProperties(fromInstance, props, propertyMatcher);
			Reflector.SetObjectProperties(toInstance, props);
		}

		#endregion
		
		#region public static void GetObjectProperties(object instance, IDictionary<string, object> properties, Predicate<PropertyInfo> propertyMatcher)

		/// <summary>
		/// Gets the property values from the given <paramref name="instance"/> and stores them in
		/// the dictionary <paramref name="properties"/>.
		/// </summary>
		/// <param name="instance">The object instance from which to read the properties.</param>
		/// <param name="properties">A dictionary that will be populated with the name-value pairs of the given properties.</param>
		/// <param name="propertyMatcher">A predicate that matches the properties that will be included in the dictionary.</param>
		/// <remarks>
		/// You need to make sure that the <paramref name="properties"/> dictionary is cleared before passed
		/// to this method. This method does not clear the dictionary.
		/// </remarks>
		public static void GetObjectProperties(object instance, IDictionary<string, object> properties, Predicate<PropertyInfo> propertyMatcher)
		{
			if (null == instance)
			{
				throw new ArgumentNullException("instance");
			}
			if (null == properties)
			{
				throw new ArgumentNullException("properties");
			}
			if (null == propertyMatcher)
			{
				throw new ArgumentNullException("propertyMatcher");
			}

			foreach (PropertyInfo propInfo in instance.GetType().GetProperties())
			{
				if (propertyMatcher.Invoke(propInfo))
				{
					object value = instance.GetType().InvokeMember(propInfo.Name, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, instance, new object[0]);
					properties.Add(propInfo.Name, value);
				}
			}
		}

		#endregion

		#region public static void SetObjectProperties(object instance, IDictionary<string, object> properties)

		/// <summary>
		/// Sets the values for the properties on the given <paramref name="instance"/>. The values
		/// of the properties are specified in the dictionary <paramref name="properties"/>.
		/// </summary>
		/// <param name="instance">The instance on which to set the properties.</param>
		/// <param name="properties">A dictionary containing the name-value pairs for the properties to set on the given <paramref name="instance"/>.</param>
		public static void SetObjectProperties(object instance, IDictionary<string, object> properties)
		{
			if (null == instance)
			{
				throw new ArgumentNullException("instance");
			}
			if (null == properties)
			{
				throw new ArgumentNullException("properties");
			}

			foreach (string key in properties.Keys)
			{
				instance.GetType().InvokeMember(
					key,
					BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
					null,
					instance,
					new object[] { properties[key] }
				);
			}
		}

		#endregion

	}
}
